package org.moonshot.cvm;

import java.util.HashMap;

public class ChattyNumber extends CVMClass implements I_ChattyCompatible
{
	private double val;
	
	private static final HashMap<String, METHOD> methods = new HashMap<String, METHOD> ();
	private static enum METHOD {
		IS("is:"), EQUALS("equals:"), GR_0(">"), GR_1("greaterThan:"), LT_0("<"), LT_1("lessThan:"),
		GRE(">="), LTE("<="), EQ_0("="), EQ_1("=="), NE_0("!="), NE_1("<>"),
		NEG_0("negative"), NEG_1("negate"), INV("inverse"),
		SIN("sin"), COS("cos"), TAN("tan"), ASIN("asin"), ACOS("acos"), ATAN("atan"), ATAN2("arctangent:"),
		ADD("+"), SUB("-"), MUL("*"), DIV("/"), POW("raisedTo:"), MOD("%"),
		FLOOR("floor"), CEIL("ceiling"), ROUNDED("rounded"), ROUNDEDTO("roundedTo:"),
		TIMESDO("timesDo:"), TODO("to:do:"), TOBYDO("to:by:do:");
		
		private METHOD(String aSignature)
		{
			methods.put(aSignature, this);
		}
		
		public static METHOD get(String aSignature)
		{
			return methods.get (aSignature);
		}
	}
	
	public ChattyNumber (int i)
    {
	    val = i;
    }
	
	public ChattyNumber (double d)
	{
		val = d;
	}
	
	public boolean doEquals (Object at)
    {
	    return at instanceof ChattyNumber && val == ((ChattyNumber)at).val;
    }
	
    public I_ChattyClass identifySelfClass (ChattyVM aVM)
    {
	    return this;
    }
    
    public Object callInstance (ChattyVM aVM, Object aInstance, String aMethod, ChattyStack aStack, int nParams)
    	throws CVMRuntimeException, CVMBlockReturnException
    {
	    if( ! (aInstance instanceof ChattyNumber) )
	    	return aVM.NAN;
	    
	    final ChattyNumber self = checkClassHandling (aVM, ChattyNumber.class, aInstance);
	    ChattyNumber param = (nParams == 1 && aStack.at (1) instanceof ChattyNumber) ? (ChattyNumber)aStack.at(1) : null;
		
		try {
			final METHOD method = METHOD.get(aMethod);
	    	if( method == null )
	        	return getSuperclass (aVM).callInstance (aVM, aInstance, aMethod, aStack, nParams);
			
    	    switch(method)
    	    {
    	    case IS: case EQUALS: case EQ_0: case EQ_1:
    	    	return aVM.convertObject (self.val == param.val);
    	    case NE_0: case NE_1:
    	    	return aVM.convertObject (self.val != param.val);
    	    case GR_0: case GR_1:
    	    	return aVM.convertObject (self.val > param.val);
    	    case GRE:
    	    	return aVM.convertObject (self.val >= param.val);
    	    case LT_0: case LT_1:
    	    	return aVM.convertObject (self.val < param.val);
    	    case LTE:
    	    	return aVM.convertObject (self.val <= param.val);
    	    	
    	    case ATAN2:
    	    	return aVM.convertObject (Math.atan2 (self.val, param.val));
    	    	
    	    case ATAN:
    	    	return aVM.convertObject (Math.atan (self.val));
    	    case ACOS:
    	    	return aVM.convertObject (Math.acos (self.val));
    	    case ASIN:
    	    	return aVM.convertObject (Math.asin (self.val));
    	    	
    	    case TAN:
    	    	return aVM.convertObject (Math.tan (self.val));
    	    case COS:
    	    	return aVM.convertObject (Math.cos (self.val));
    	    case SIN:
    	    	return aVM.convertObject (Math.sin (self.val));
    	    	
    	    case NEG_0: case NEG_1:
    	    	return aVM.convertObject (-self.val);
    	    	
    	    case INV:
    	    	return aVM.convertObject (1.0 / self.val);
    	    	
    	    case ADD:
    	    	return aVM.convertObject (self.val + param.val);
    	    case SUB:
    	    	return aVM.convertObject (self.val - param.val);
    	    case MUL:
    	    	return aVM.convertObject (self.val * param.val);
    	    case DIV:
    	    	return aVM.convertObject (self.val / param.val);
    	    case POW:
    	    	return aVM.convertObject (Math.pow (self.val, param.val));
    	    case MOD:
    	    	return aVM.convertObject (self.val - Math.floor(self.val / param.val) * param.val);
    	    	
    	    case FLOOR:
    	    	return aVM.convertObject (Math.floor(self.val));
    	    case CEIL:
    	    	return aVM.convertObject (Math.ceil (self.val));
    	    	
    	    case ROUNDED:
    	    	return aVM.convertObject (Math.round (self.val));
    	    case ROUNDEDTO:
    	    	return aVM.convertObject (param.val * Math.round(self.val / param.val));
    	    	
    	    case TIMESDO:
    	    	CVMBlock block = (CVMBlock) aStack.at (1);
    	    	for(double n = 0; n < self.val; n += 1)
    	    		aVM.execute (block, aStack, 0);
    	    	return aVM.NIL;
    	    
    	    case TODO:
    	    	param = (ChattyNumber) aStack.at(2);
    	    	return toByDo(aVM, self.val, param.val, Math.signum (param.val - self.val), (CVMBlock)aStack.at(1));
    	    	
    	    case TOBYDO:
    	    	return toByDo(aVM, self.val, param.val, ((ChattyNumber)aStack.at(2)).val, (CVMBlock)aStack.at(1));
    	    }
		}
		catch (ClassCastException e)
		{
			throw new CVMRuntimeException (e);
		}
		catch (NullPointerException e)
		{
			throw new CVMRuntimeException (e);
		}
	    
	    return getSuperclass (aVM).callInstance (aVM, aInstance, aMethod, aStack, nParams);
    }
    
    private Object toByDo (ChattyVM aVM, double from, double to, double by, CVMBlock block)
    	throws CVMRuntimeException, CVMBlockReturnException
    {
    	final Object[] arr = new Object[1];
    	final ChattyNumber cell = new ChattyNumber (from);
	    Object eval = null;
    	arr[0] = cell;
	    
	    if( by > 0 && to > from )
	    	for(; cell.val < to; cell.val += by)
	    		eval = aVM.execute(block, arr);
	    else if( by < 0 && to < from )
	    	for(; cell.val > to; cell.val += by)
	    		eval = aVM.execute(block, arr);
    	
	    return eval;
    }
    
	public I_ChattyClass getSuperclass (ChattyVM aVM)
    {
	    return aVM.classFor(ChattyBasic.class);
    }
	
}
